lister.t

documentation
#charset "us-ascii"
#include "advlite.h"


/*
 *   ***************************************************************************
 *   lister.t
 *
 *   This module forms part of the adv3Lite library (c) 2012-13 Eric Eve, and is
 *   substantially borrowed from the Mercury library (c) 2012 Michael J.
 *   Roberts.
 */


/*
 *   Lister is the class that displays lists of objects.  This is used in
 *   room descriptions, inventory lists, and EXAMINE descriptions of
 *   objects (to show the examined object's contents).
 *   
 *   Showing a listing is basically a function call.  The reason we make a
 *   whole class out of it is that we provide a number of options, and a
 *   class is a convenient way to specify options.  The options are simply
 *   defined as properties of a lister object, so to create a certain kind
 *   of list, you just set up a Lister instance with the desired options.
 *   We provide pre-defined Lister objects for the common library listing
 *   types, but games can create their own custom list types by creating
 *   their own Lister objects for different sets of options.  
 */
class Lister: object
    /*
     *   Show a list of objects.  'lst' is the list of objects to show;
     *   'paraCnt' is the number of paragraph-style descriptions that we've
     *   already shown as part of this description.
     *
     *   Note that many specific listers replaced the 'paraCnt' parameter with a
     *   more useful 'parent' parameter, containing the identity of the object
     *   whose contents are being listed.
     */
    show(lst, paraCnt, paraBrk = true)
    {
        /* get the subset that passes our 'listed' test */
        lst = lst.subset({ o: listed(o) });
        
        /* if we have any items, show them */
        if (lst.length() > 0)
        {
            /* sort into listing order */
            lst = lst.sort(SortAsc, { a, b: listOrder(a) - listOrder(b) });
            
            /* 
             *   The list is plural if it has multiple items, or a single item
             *   that has plural usage.
             */
            local pl = (lst.length() > 1 || lst[1].plural);
            
            /* Show the list prefix */
            showListPrefix(lst, pl, paraCnt);
            
            /* show the items */
            showList(lst, pl, paraCnt);
            
            /* Show the list suffix. */
            showListSuffix(lst, pl, paraCnt);
            
            /* add a paragraph break at the end, if it's requested */
            if(paraBrk)
                "<.p>";
        }
        else
            showListEmpty(paraCnt);
    }
    
    showListPrefix(lst, pl, paraCnt)  { }
    
    showListSuffix(lst, pl, paraCnt)  { }
    
    showListEmpty(paraCnt)  { }
    
     
    /*
     *   Should 'obj' be listed in this list?  Returns true if so, nil if not.
     *   By default, we list any object whose 'listed' property is true.
     */
    listed(obj) { return obj.listed; }
    
    /*
     *   Get an item's sorting order.  This returns an integer that gives the
     *   relative position in the list; we order the list in ascending order of
     *   this value.  By default, we return the 'listOrder' property of the
     *   object.
     */
    listOrder(obj) { return obj.listOrder; }
    
    
    
    /* 
     *   Return a string containing what this lister would display, minus the
     *   terminating paragraph break.
     */
    buildList(lst)
    {
        local str = gOutStream.captureOutput({: show(lst, 0, nil) });
        
        return str;
    }
    
;

/* 
 *   An Item Lister is a lister used for listing physical items. Notice that
 *   most of the specifics of the listers defined below are language-specific,
 *   and so are defined in the language-specific part of the library (e.g. in
 *   english.t).
 */
class ItemLister: Lister
    
    /*
     *   Show a list of objects.  'lst' is the list of objects to show; 'parent'
     *   parameter is the object whose contents are being listed, 'paraBrk'
     *   defines whether or not we want a paragraph break after the list.
     */
    show(lst, parent, paraBrk = true)
    {
        /* Carry out the inherited handling */
        inherited(lst, parent, paraBrk);
        
        /* Note that every item in our list has been mentioned and seen, provided it's listed. */
        foreach(local cur in lst)
        {
            if(listed(cur))
            {
                cur.mentioned = true;
                cur.noteSeen();
            }
        }        
    }
    
    showList(lst, pl, parent)   
    {    
        lst = findListGroups(lst);
        
        /* 
         *   If our groupTab table is empty, there are no ListGroups involved in our list, so we can
         *   just show a simple list of items.
         */
        if(groupTab.getEntryCount() == 0)
            showSimpleList(lst);
        
        /* Otherwise we'll need to use the more commplex list handling. */
        else
            showComplexList(lst, pl, parent);
    }
    
    /* 
     *   Show a simple list of things. This must be defined in the language-specific part of the
     *   library.
     */
    showSimpleList(lst)     {  }
    
    /* Our LookUp table of any ListGroups we need to work with. */
    groupTab = nil
    
    findListGroups(lst)
    {
        /* Create a new LookupTable for our table of ListGroups */
        groupTab =  new LookupTable;
        
        /* 
         *   For every item we're due to list, if it has any ListGroups listed in its listWith
         *   property, create or update an entry for that ListGroup and the item it lists in our
         *   table of ListGroups.
         */
        foreach(local item in lst)
        {
            foreach(local grp in valToList(item.listWith))
                groupTab[grp] = valToList(groupTab[grp]) + item;                    
        }        
      
        /* Get a list of the groups that have been added to groupTab. */
        local grpList = groupTab.keysToList();
        
        /* 
         *   Then remove from the list group table entries for any ListGroups that don't match the
         *   minimum number of items they should list.
         */
        foreach (local grp in grpList)
        {
            if(groupTab[grp].length < grp.minGroupSize)
                groupTab.removeElement(grp);
        }
        
        /* 
         *   If there's nothing left in the list group table, there's nothing more to do here, so
         *   simply return our list of items unchanged.
         */
        if(groupTab.getEntryCount() == 0)
            return lst;
        
        /* 
         *   Get an updated list of the groups that are left, in descending order of priority and
         *   the number of items they relate to.
         */
        grpList = groupTab.keysToList().sort(SortAsc, {a, b: b.priority + groupTab[b].length - 
                                             a.priority - groupTab[a].length});
        
        local listed = [];
        
        /* 
         *   Check for items that appear in more than one ListGroup. Remove their association from
         *   the groupTab table, and if that leaves a ListGroup with too few items, remove that too.
         */
        foreach(local grp in grpList)
        {
            /* Note the items covered by the current ListGroup grp */
            local items = groupTab[grp];
            
            /* 
             *   Remove items from the list that would already have been listed in previous
             *   ListGroups.
             */
            items = items.subset({x: listed.indexOf(x) == nil});
            
            /*  Update the groupTab entry for this group with the revised list of items. */
            groupTab[grp] = items;                       
                  
            /* 
             *   If that leaves the group with too few items for the group to be used, remove it
             *   from our group table.
             */
            if(items.length < grp.minGroupSize)
                groupTab.removeElement(grp);
            
            /* Otherwise, add its items to the list of items that will be listed by ListGroups. */
            else
                listed = listed.appendUnique(items); 
        }
        
        /* 
         *   Remove the list of items to be listed by ListGroups from the list of items to be listed
         *   and add the ListGroups in their place.
         */
        lst = lst - listed + groupTab.keysToList();
        
        /* Sort the resulting list in ascending order of listOrder. */
        lst = lst.sort(SortAsc, {a, b: a.listOrder - b.listOrder});
        
        /* Return the resulting list. */
        return lst;
    }
    
    /* Show a list of items that may be a list of Things and ListGroups */
    showComplexList(lst, pl, parent)
    {
        /* Note the length of the list. */
        local len = lst.length();
        
        /* Iterate through the list. */
        for(local i = 1, local item in lst;; ++ i)    
        { 
            /* If we're reaching the final item in the list, prepend ' and ' */
            if(i == len && i > 1)
                DMsg(list and, ' and ');
            
            /* Note the punctuation that should follow this item. */
            local punct = ', ';
            
            /* If the item is a Thimg, simpyl display its listName. */
            if(item.ofKind(Thing))
            {                
                "<<listName(item)>>";
            }
            
            /* 
             *   If it's a ListGroup, get the ListGroup to list its contents and then change the
             *   following punctuation to a semicolon.
             */
            if(item.ofKind(ListGroup))
            {
                item.showList(self, groupTab[item]);
                punct = '; ';
            }
            
            /* If we haven't reach the end of the list, add the appropriate punctuation. */
            if(i < len)
                say(punct);
        }   
            
    }
    
    /* 
     *   The property on a Thing-derived container to test whether its contents
     *   should be listed when listing with this lister
     */
    contentsListedProp = &contentsListed
    
    /* 
     *   Flag, so we want to list contents of contents when using this lister;
     *   by default we do.
     */
    listRecursively = (gActor == gPlayerChar)   
;


/*
 *   lookLister displays a list of miscellaneous objects in a room description.
 */
lookLister: ItemLister
    /* is the object listed in a LOOK AROUND description? */
    listed(obj) { return obj.lookListed && !obj.isHidden; }
;

/* 
 *   lookContentsLister is used to display a list of the contents of objects in
 *   a room description.
 */
lookContentsLister: ItemLister
    /* is the object listed in a LOOK AROUND description? */
    listed(obj) { return obj.lookListed && !obj.isHidden; }
    
    contentsListedProp = &contentsListedInLook
;

/*
 *   inventoryLister displays an inventory listing in WIDE format.
 */
inventoryLister: ItemLister
    /* is the object listed in an inventory list? */
    listed(obj) { return obj.inventoryListed && !obj.isHidden; }
;

/* wornLister displays a list of items being worn. */
wornLister: ItemLister
     /* is the object listed in an inventory list? */
    listed(obj) { return obj.inventoryListed && !obj.isHidden; }
;

/*
 *   inventoryTallLister for displaying an inventory list in TALL format.
 */
inventoryTallLister: ItemLister
    /* is the object listed in an inventory list? */
    listed(obj) { return obj.inventoryListed && !obj.isHidden; }
    
     showList(lst, parent, paraBrk = true)
    {
        /* list the inventory using the inventory tall format. */
        showContentsTall(lst, parent, paraBrk);
        
        
        /* Ensure the indentation level is reset to zero once we've finished listing */
        indentLevel = 1;   
        
    }
    
    
    /* 
     *   List the player's inventory in tall format, i.e., as a columnar list with each item on a
     *   new line. This method may call itself recursively to list subcontents (such as the visible
     *   contents of any containers in the player character's inventory).
     */
    showContentsTall(lst, parent, paraBrk = true) 
    {
        foreach(local cur in lst)
        {
            /* Carry out the indenting for sublisting contents */
            for(local i in 1..indentLevel)            
                "\t";
            
            /* Display the appropriate name for the listed item */
            say(listName(cur));
            
            /* Move to a new line */
            "\n";
            
            /* Note that every item in our list has been mentioned and seen */
            cur.mentioned = true;
            cur.noteSeen();  
            
            /* 
             *   If we want to list recursively and we haven't yet reached out maximum indentation
             *   (i.e., nesting) level, then build a list of subcontents and then display it.
             */
            if(listRecursively && indentLevel < maxIndentLevel)
            {
                /* 
                 *   Get a list of the current item's listable contents. If we can't see in this is
                 *   an empty list.
                 */
                local subList = (cur.contType == In && !cur.canSeeIn) 
                    ? [] : cur.contents.subset({o: listed(o) });
                
                /*   If we have an open or transparent subcontainer, add its contents. */ 
                if(cur.remapIn && cur.remapIn.canSeeIn)
                    subList += cur.remapIn.contents.subset({o: listed(o) });
                
                /*   If we have a subsurface, add its contents. */ 
                if(cur.remapOn)
                    subList += cur.remapOn.contents.subset({o: listed(o) });
                
                
                /* 
                 *   If this list isn't empty, then display this list of subcontents as a column of
                 *   items indented under their containing item.
                 */
                if(subList.length > 0)
                {
                    /* increment the indentation level. */
                    indentLevel++;
                    
                    /* sort the list of subcontents in ascending order of their listOrder property */
                    subList = subList.sort(true, {x, y: y.listOrder - x.listOrder});
                    
                    /* call this method recursively to list the subcontents. */
                    showContentsTall(subList, cur, paraBrk);
                    
                    /* decrement the indentation level once we've finished listing. */
                    indentLevel-- ;
                    
                }
            }
        }
        
    }    
    
    
    /* 
     *   A version of the listName method that doesn't list an items contents in parenthesis after
     *   its name, which would be inappropriate to the tall inventory format.
     */
    listName(o)
    {
        /* 
         *   When we're doing a tall inventory listing we don't want to list sucontents after the
         *   name of each item, so we store the current value of showSubListing, then set
         *   showSublisting to nil before carrying out the inherited handling, and then finally
         *   restore the original value of ShowSubListing.
         */
        
        local ssl = showSubListing;
        
        showSubListing = nil;
        
        local lnam = inherited(o);
        
        showSubListing = ssl;
        
        return lnam;          
        
    }
    
    /* The current indentation level for listing subcontents recursively */
    indentLevel = 1
    
    /* The maximum level of indentation we want to allow for listed nested subcontents. */
    maxIndentLevel = 5
    
    /* 
     *   The property on a Thing-derived container to test whether its contents
     *   should be listed when listing with this lister
     */
    contentsListedProp = &contentsListed
    
    /* 
     *   Flag, so we want to list contents of contents when using this lister;
     *   by default we do if the actor is the player character.
     */
    listRecursively = (gActor == gPlayerChar)
    
    showListPrefix(lst, pl, paraCnt)  { DMsg(list tall prefix, '\n{I} {am} carrying:\n '); }
    
    showListSuffix(lst, pl, paraCnt)  { }
    
    showListEmpty(paraCnt)  { DMsg(list tall empty, '\n{I} {am} empty-handed. '); }
    
;





/*
 *   descContentsLister displays a list of miscellaneous contents of an object
 *   being examined.  
 */
descContentsLister: ItemLister
    /* is the object listed in an EXAMINE description of its container? */
    listed(obj) { return obj.examineListed && !obj.isHidden; }

    contentsListedProp = &contentsListedInExamine
;

/* 
 *   openingContentsLister displays the contents of an openable container when
 *   it is first opened.
 */
openingContentsLister: ItemLister
    /* is the object listed in an EXAMINE description of its container? */
    listed(obj) { return obj.examineListed && !obj.isHidden; }
    
    /* 
     *   We don't want recursive listing with the openingContentsLister, since
     *   this can produce odd results.
     */
    listRecursively = nil
;

/* 
 *   lookInLister is used to list the contents of an object in response to LOOK
 *   IN/UNDER/BEHIND
 */
lookInLister: ItemLister
    /* 
     *   is the object listed in a SEARCH/LOOK IN/UNDER/BEHIND description of
     *   its container?
     */
    listed(obj) { return obj.searchListed && !obj.isHidden; }

    contentsListedProp = &contentsListedInSearch

;

/* A lister used to list the items attached to a SimpleAttachable */
simpleAttachmentLister: ItemLister
    /* an object is listed if it's attached */
    listed(obj) { return obj.attachedTo != nil && !obj.isHidden; }
    
;

/*  A lister used to list the items plugged into a PlugAttachable */
plugAttachableLister: simpleAttachmentLister
;

/* 
 *   A lister that can be readily customized to tailor the text before and after
 *   a list of miscellaneous items in a room description.
 */
class CustomRoomLister: ItemLister
    
    /* is the object listed in a LOOK AROUND description? */
    listed(obj) { return obj.lookListed && !obj.isHidden; }
    
    /* 
     *   In the simple form of the constructor, we just supply a string that
     *   will form the prefix string for the lister. In the more sophisticated
     *   form we can supply an additsion argument that's an anonymous method or
     *   function that's used to show the list prefix or suffix, or else just
     *   the suffix string.
     */
    construct(prefix, prefixMethod:?, suffix:?, suffixMethod:?)
    {
        prefix_ = prefix;
        
        if(prefixMethod != nil)
            setMethod(&showListPrefix, prefixMethod);
        
        if(suffix != nil)
            suffix_ = suffix;
        
        if(suffixMethod != nil)
            setMethod(&showListSuffix, suffixMethod);
    }
    
    prefix_ = nil
    suffix_ = '. '
    
    showListPrefix(lst, pl, irName)  
    { 
        "<.p><<prefix_>> ";
    }
    
    showListSuffix(lst, pl, irName)  
    { 
        "<<suffix_>>";
    }
    
    showSubListing = (gameMain.useParentheticalListing)
;


/*
 *   -----------------------------------------------------------------------------------
 *
 *   Various types of ListGroup
 *
 *   These can be defined on the listWith property of miscellaneous items to be listed in a room,
 *   container or inventory listing to group items together.
 */


/* 
 *   Abstract base class for all kinds of ListGroup. Game code will use one of more of its
 *   subclasses defined below.
 */
class ListGroup: object
    
    /* 
     *   The minimum number of objects in the list to be listed belonging to this ListGroup for this
     *   ListGroup to be used to list them. By default this is two.
     */
    minGroupSize = 2    
    
    
    /* Show a list of items (lst) using lister. */
    showList(lister, lst)
    {
        showSimpleList(lister, lst);
    }
    
    /* 
     *   Show a simple list of items (lst) using lister. The method of doing so must be provided in
     *   the language-dependent part of the libary.
     */    
    showSimpleList(lister, lst)   {}
    
    /* 
     *   Our own list order, relative to the listOrder of other items in the list to be listed that
     *   do not belong to this ListGroup.
     */
    listOrder = 100
    
    /* 
     *   Normally if an item to be listed belongs to more than one ListGroup, we use the ListGroup
     *   that would contain the largest number of items. We can override this by assigning a higher
     *   priority to the ListGroup so that the highet priority ListGroup will be use. This should be
     *   a number that's large compared with the number of items in any one group so we use a
     *   default of 100. Game code may wish to assign priorities in steps of a hundred.
     */
    priority = 100
;


/* A ListGroupSorted simply lists its items in ascending listOrder sequence within the group. */
class ListGroupSorted: ListGroup
    
    /* 
     *   The compareGroupItems method defines how items will be sorted within the ListGroup. By
     *   default we sort on their groupOrder, which by default is the same as their listOrder,  but
     *   we could define an alternative custom property or some other means of sorting if we so
     *   wished.
     */
    compareGroupItems (a, b) { return a.groupOrder - b.groupOrder; }    
    
    /* Show our list (lst) of items using lister. */
    showList(lister, lst)
    {
        /* First sort our items. */
        lst = sortList(lst);
        
        /* Then show a simple list of them using lister. */
        showSimpleList(lister, lst);
    }
    
    /* Sort our list of items. By default we do so in their listOrder order. */
    sortList(lst) { return lst.sort(SortAsc, {a, b: compareGroupItems(a, b)});  }
;


/* 
 *   ListGroupSuffixPrefix is a type of ListGroupSorted to which we can prepend or append custom
 *   text, e.g. "keys: "
 */
class ListGroupSuffixPrefix: ListGroupSorted
    
    /* 
     *   The prefix text we want to display before our list of items, if any. This is for game code
     *   to define.
     */
    groupPrefix = nil
        
    /* 
     *   The suffix text we want to display after our list of items, if any. This is for game code
     *   to define.
     */
    groupSuffix = nil
    
    /*  
     *   Show our group prefix. By default we display a apelled out count of the number of items
     *   this group is going to list followed by our prefix text, but game code can override if some
     *   other format is wanted.
     */
    showGroupPrefix(pov, lst)         
    { 
        /* Spell out the number of items we're about to list. */
        "<<spellNumber(lst.length)>> ";
        
        /* Then display our group prefix/ */
        display(&groupPrefix); 
    }
    
    /* Display our group suffix. */
    showGroupSuffix(pov, lst) { display(&groupSufffix); }
          
    /* Show our list of items (lst) using lister. */
    showList(lister, lst)
    {
        /* Display our group prefix. */
        showGroupPrefix(gActor, lst); 
        
        /* Sort out list */
        lst = sortList(lst);
        
        /* Output a apace to separate the list from our prefix. */
        " ";
        
        /* Show a simple list of our items (lst) using lister. */
        showSimpleList(lister, lst);
        
        /* Output another space to separate the list from its suffix */
        " ";
        
        /* Show the group suffix. */
        showGroupSuffix(gActor, lst);
    }        
        
;


/* 
 *   ListGroupParem introduces the list with a summary of what it contains (e.g., 'three keys') and
 *   then lists its individual items in parentheses (e.g. 'three keys (a large key, a brass key, and
 *   a tiny silver key).
 */
class ListGroupParen: ListGroupSorted
    
    /* Show the summmary prefix, e.g. "three keys". */
    showGroupCountName(lst)
    {
        "<<spellNumber(lst.length)>> <<pluralName>>";
    }     
    
    /* The plural name to use to summarise the items in the group, e.g., 'keys' */
    pluralName = ''
    
    /* Show our list of items (lst) using lister. */
    showList(lister, lst)
    {
        /* Start with our summary description, e.g., 'three keys' */
        showGroupCountName(lst);
        
        /* Sort our list of items. */
        lst = sortList(lst);
        
        /* Display the opening parenthesis. */
        " (";
        
        /* Show a simple list of our itmes (lst) using lister. */
        showSimpleList(lister, lst);
        
        /* Display the closing parenthesis. */
        ")";
    }
;

/* A ListGroupCuatom displays the list or a summary of the list in a user-defined way, */

class ListGroupCustom: ListGroup
    
    /* The text to display to define this group. Tbis must be defined in user code. */
    showGroupMsg(lst) { }
    
    /* Show our list. */
    showList(lister, lst)
    {
        /* Call showGroupMsg(lst) to do the work of displaying it. */
        showGroupMsg(lst);
    }
;
Adv3Lite Library Reference Manual
Generated on 13/06/2025 from adv3Lite version 2.2.1